# Copyright (c) 2025, qiushike and contributors
# For license information, please see license.txt

import frappe
from frappe.model.document import Document
from frappe import _
from light_mes.light_mes.overrides.work_order import create_job_card
from frappe.model.mapper import get_mapped_doc
from frappe.utils import flt


class WipTransfer(Document):
	def validate(self):
		self.validate_return_quantities()
		self.validate_transfer_qty()
		self.calculate_total_qty()
	
	def validate_return_quantities(self):
		"""校验退货单的数量必须是负数"""
		if self.is_return:
			for item in self.wip_transfer_item:
				# 校验良品数量必须是负数
				if item.qty >= 0:
					frappe.throw(_("Row #{0}: For return document, quantity must be negative. Current: {1}")
						.format(item.idx, item.qty))
				
				# 如果有废品数量字段，也校验必须是负数或零
				if hasattr(item, 'scrap_qty') and item.scrap_qty != 0:
					frappe.throw(_("Row #{0}: For return document, scrap quantity must be zero. Current: {1}")
						.format(item.idx, item.scrap_qty))
				
				# 如果有总数量字段，也校验必须是负数
				if hasattr(item, 'total_qty') and item.total_qty >= 0:
					frappe.throw(_("Row #{0}: For return document, total quantity must be negative. Current: {1}")
						.format(item.idx, item.total_qty))
	
	def validate_transfer_qty(self):
		"""校验发料和入库数量，考虑退货情况"""
		# 如果是退货单，校验退货数量不能大于原始单据数量
		if self.is_return and self.return_against:
			for item in self.wip_transfer_item:
				if not item.wip_transfer_item:
					frappe.throw(_("Row #{0}: Original Wip Transfer Item is required for return").format(item.idx))
					
				# 获取原始单据明细
				original_item = frappe.get_doc("Wip Transfer Item", item.wip_transfer_item)
				
				# 直接使用原始单据明细中的returned_qty字段
				returned_qty = original_item.returned_qty or 0
				
				# 计算可退货数量
				returnable_qty = original_item.qty - returned_qty
				
				# 校验退货数量不能大于可退货数量
				if abs(item.qty) > returnable_qty:
					frappe.throw(_("Row #{0}: Return quantity ({1}) cannot exceed returnable quantity ({2})")
						.format(item.idx, abs(item.qty), returnable_qty))
				
				# 废品数量不能退货，如果有废品数量，抛出错误
				if hasattr(item, 'scrap_qty') and item.scrap_qty != 0:
					frappe.throw(_("Row #{0}: Scrap quantity cannot be returned").format(item.idx))

		# 原有的校验逻辑，需要考虑退货数量
		elif self.type == "Send to Supplier":
			for item in self.wip_transfer_item:
				# 获取工单信息
				job_card = frappe.get_doc("Job Card", item.job_card)
				work_order = frappe.get_doc("Work Order", job_card.work_order)
				
				# 获取当前工序和上工序信息
				current_operation = None
				prev_operation = None
				
				# 先找到当前工序
				for op in work_order.operations:
					if op.operation == job_card.operation:
						current_operation = op
						break
				
				if not current_operation:
					continue
				
				# 根据idx找到上一道工序
				if current_operation.idx > 1:  # 如果不是第一道工序
					for op in work_order.operations:
						if op.idx == current_operation.idx - 1:
							prev_operation = op
							break
				
				if prev_operation and prev_operation.custom_enable_wip_receipt:
					# 获取当前工序已发料数量（减去已退料数量）
					sent_qty = (frappe.db.get_value(
						"Work Order Operation",
						current_operation.name,
						"custom_sent_qty"
					) or 0) - (frappe.db.get_value(
						"Work Order Operation",
						current_operation.name,
						"custom_sent_returned_qty"
					) or 0)
					
					# 获取上工序WIP入库数量
					prev_wip_qty = frappe.db.get_value(
						"Work Order Operation", 
						prev_operation.name,
						"custom_wip_qty"
					) or 0
					
					if (sent_qty + item.qty) > prev_wip_qty:
						frappe.throw(
							_("Row #{0}: Total sent quantity ({1}) cannot exceed WIP receipt quantity ({2}) of previous operation ({3})")
							.format(
								item.idx, 
								sent_qty + item.qty, 
								prev_wip_qty,
								prev_operation.operation
							)
						)
				
				# 在同一个循环中检查是否允许超发
				if not self.allow_over_issue and item.qty > job_card.for_quantity:
					frappe.throw(
						_("Row #{0}: Issue quantity ({1}) cannot exceed Job Card quantity ({2}). Please check 'Allow Over Issue' if you want to issue more.")
						.format(
							item.idx,
							item.qty,
							job_card.for_quantity
						)
					)
		# 校验车间入库的入库数量不能大于加工单的工单数量,车间入库的良品数量不能大于加工单的完工数量
		elif self.type == "Receive from Workshop":
			for item in self.wip_transfer_item:
				job_card = frappe.get_doc("Job Card", item.job_card)
				if item.qty > job_card.total_completed_qty:
					frappe.throw(
						_("Row #{0}: Quality quantity ({1}) cannot exceed Total completed quantity ({2})")
						.format(item.idx, item.qty, job_card.total_completed_qty)
					)
				if item.total_qty > job_card.for_quantity:
					frappe.throw(
						_("Row #{0}: Total quantity ({1}) cannot exceed Work Order quantity ({2})")
						.format(item.idx, item.total_qty, job_card.for_quantity)
					)
		# 校验委外入库的数量
		elif self.type == "Receive from Supplier":
			for item in self.wip_transfer_item:
				# 直接使用 operation_id 获取工序明细
				if not item.operation_id:
					continue
					
				# 获取当前工序已发料数量、已入库数量和已废品数量
				operation = frappe.get_doc("Work Order Operation", item.operation_id)
				
				# 获取已发料数量（减去已退料数量）
				sent_qty = (operation.custom_sent_qty or 0) - (operation.custom_sent_returned_qty or 0)
				
				# 获取已入库数量（减去已退库数量）
				received_qty = (operation.custom_received_qty or 0) - (operation.custom_received_returned_qty or 0)
				
				# 获取已废品数量
				scrapped_qty = operation.custom_scrapped_qty or 0
				
				# 计算本次入库后的总入库数量和废品数量
				total_received_qty = received_qty + item.qty
				total_scrapped_qty = scrapped_qty + item.scrap_qty
				
				# 校验良品数量+废品数量不能超过发料数量
				if (total_received_qty + total_scrapped_qty) > sent_qty:
					frappe.throw(
						_("Row #{0}: Total received quantity ({1}) and scrapped quantity ({2}) cannot exceed sent quantity ({3})")
						.format(
							item.idx, 
							total_received_qty, 
							total_scrapped_qty,
							sent_qty
						)
					)

	def calculate_total_qty(self):
		"""自动计算入库数量"""
		if self.type == "Receive from Workshop" or self.type == "Receive from Supplier":
			for item in self.wip_transfer_item:
				item.total_qty = item.qty + item.scrap_qty

	def on_submit(self):
		"""提交时更新加工单和工单数据，以及原始单据的退货数量"""
		self.update_job_card_and_work_order()
		
		# 如果是退货单，更新原始单据明细的退货数量
		if self.is_return and self.return_against:
			self.update_return_quantities(cancel=False)

	def on_cancel(self):
		"""取消时回滚加工单和工单数据，以及原始单据的退货数量"""
		self.update_job_card_and_work_order()
		
		# 如果是退货单，回滚原始单据明细的退货数量
		if self.is_return and self.return_against:
			self.update_return_quantities(cancel=True)

	def update_job_card_and_work_order(self):
		"""更新加工单和工单数据"""
		for item in self.wip_transfer_item:
			if item.job_card:
				job_card = frappe.get_doc("Job Card", item.job_card)
				original_qty = job_card.for_quantity
				work_order = frappe.get_doc("Work Order", item.work_order)
				
				# 找到对应的工序
				for operation in work_order.operations:
					if operation.name == job_card.operation_id:
						if self.type == "Receive from Workshop":
							# 更新工序WIP入库数量
							operation.custom_wip_qty = (operation.custom_wip_qty or 0) + (item.qty if self.docstatus == 1 else -item.qty)
							# 更新工序废品数量
							operation.custom_scrapped_qty = (operation.custom_scrapped_qty or 0) + (item.scrap_qty if self.docstatus == 1 else -item.scrap_qty)
							# 保存工单以更新工序数据
							work_order.save()
							
						elif self.type == "Send to Supplier":
							if not self.is_return:
								# 更新工序委外发料数量
								operation.custom_sent_qty = (operation.custom_sent_qty or 0) + (item.qty if self.docstatus == 1 else -item.qty)
							else:
								# 更新工序委外发料 退货数量
								operation.custom_sent_returned_qty = (operation.custom_sent_returned_qty or 0) - (item.qty if self.docstatus == 1 else -item.qty)
							# 保存工单以更新工序数据
							work_order.save()
							
						elif self.type == "Receive from Supplier":
							if not self.is_return:
								# 更新工序委外入库数量
								operation.custom_received_qty = (operation.custom_received_qty or 0) + (item.qty if self.docstatus == 1 else -item.qty)
								# 更新工序废品数量
								operation.custom_scrapped_qty = (operation.custom_scrapped_qty or 0) + (item.scrap_qty if self.docstatus == 1 else -item.scrap_qty)
								# 更新工序的委外加工费custom_subcontracting_fee，等于工序的委外单价乘以工序的委外入库良品数量
								operation.custom_subcontracting_fee = flt(operation.custom_service_price * (operation.custom_received_qty - operation.custom_received_returned_qty))
								# 更新工单的委外加工费custom_subcontracting_fee
								work_order.calculate_total_subcontracting_fee()
								# 保存工单以更新工序数据
								work_order.save()
								
								
								if self.docstatus == 1:
									# 重新获取加工单以确保有最新的数据
									job_card.for_quantity = item.total_qty

									
									# 为加工单添加工时记录
									if not job_card.time_logs:
										# 创建工时记录
										job_card.append("time_logs", {
											"completed_qty": item.qty,
											"time_in_mins": 0,  # 委外加工不记录时间
										})
										# 保存加工单
										job_card.save()
									
									# 提交加工单
									job_card.submit()
									
									# 如果还有剩余数量,创建新的加工单
									remaining_qty = original_qty - job_card.for_quantity
									if remaining_qty > 0 and self.is_partially_received:
										
										# 创建新的加工单
										new_job_card = create_job_card(
											work_order=work_order,
											row={
												"name": operation.name,
												"operation": job_card.operation,
												"workstation": job_card.workstation,
												"workstation_type": job_card.workstation_type,
												"hour_rate": job_card.hour_rate,
												"serial_no": job_card.serial_no,
												"sequence_id": job_card.sequence_id,
												"job_card_qty": remaining_qty,
												"wip_warehouse": job_card.wip_warehouse,
												"source_warehouse": getattr(operation, 'source_warehouse', None),
												"custom_is_subcontracting": job_card.custom_is_subcontracting,
												"custom_supplier": job_card.custom_supplier,
												"custom_service_price": job_card.custom_service_price,
												"custom_enable_wip_receipt": job_card.custom_enable_wip_receipt,
												"custom_plant_floor": job_card.custom_plant_floor,
											},
											auto_create=True
										)
								elif self.docstatus == 2:
									job_card.cancel()

							else:
								# 更新工序委外入库 退货数量
								operation.custom_received_returned_qty = (operation.custom_received_returned_qty or 0) - (item.qty if self.docstatus == 1 else -item.qty)
								# 更新工序的委外加工费custom_subcontracting_fee，等于工序的委外单价乘以工序的委外入库良品数量
								operation.custom_subcontracting_fee = flt(operation.custom_service_price * (operation.custom_received_qty - operation.custom_received_returned_qty))
								# 更新工单的委外加工费custom_subcontracting_fee
								work_order.calculate_total_subcontracting_fee()
								# 保存工单以更新工序数据
								work_order.save()


						break
	
	def update_return_quantities(self, cancel=False):
		"""更新原始单据明细的退货数量"""
		for item in self.wip_transfer_item:
			if not item.wip_transfer_item:
				continue
				
			# 获取原始单据明细
			original_item = frappe.get_doc("Wip Transfer Item", item.wip_transfer_item)
			
			# 计算退货数量变化
			qty_change = abs(item.qty)  # 退货单中的数量是负数，取绝对值
			
			# 更新退货数量
			if cancel:
				# 取消退货单，减少退货数量
				new_returned_qty = (original_item.returned_qty or 0) - qty_change
			else:
				# 提交退货单，增加退货数量
				new_returned_qty = (original_item.returned_qty or 0) + qty_change
				
			# 检验退货数量不能为负数
			if new_returned_qty < 0:
				frappe.throw(_("Returned quantity cannot be negative for Wip Transfer Item {0}. Current: {1}, Change: {2}")
					.format(item.wip_transfer_item, original_item.returned_qty or 0, qty_change))
			
			# 更新原始单据明细的退货数量
			frappe.db.set_value("Wip Transfer Item", item.wip_transfer_item, "returned_qty", new_returned_qty)

@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def get_subcontracting_suppliers(doctype, txt, searchfield, start, page_len, filters):
	"""获取在草稿加工单中的供应商列表"""
	return frappe.db.sql("""
		SELECT DISTINCT custom_supplier 
		FROM `tabJob Card`
		WHERE docstatus = 0 
		AND company = %(company)s
		AND custom_supplier IS NOT NULL
		AND custom_supplier LIKE %(txt)s
	""", {
		'company': filters.get('company'),
		'txt': '%%%s%%' % txt
	})

def get_invoiced_qty_map(wip_transfer_doc=None, wip_transfer_name=None):
	"""获取已开票数量映射"""
	invoiced_qty_map = {}
	
	# 如果传入的是文档名称而不是文档对象，则获取文档对象
	if not wip_transfer_doc and wip_transfer_name:
		wip_transfer_doc = frappe.get_doc("Wip Transfer", wip_transfer_name)
	
	# 如果没有文档对象，返回空映射
	if not wip_transfer_doc:
		return invoiced_qty_map
	
	# 直接从文档的items子表中获取数据
	for item in wip_transfer_doc.wip_transfer_item:
		invoiced_qty_map[item.name] = flt(item.invoiced_qty)
	
	return invoiced_qty_map

@frappe.whitelist()
def make_purchase_invoice(source_name, target_doc=None, selected_items=None):
	"""从在制品移转创建采购发票"""
	from erpnext.accounts.party import get_payment_terms_template
	
	doc = frappe.get_doc("Wip Transfer", source_name)
	
	# 检查是否为委外入库类型
	if doc.type != "Receive from Supplier":
		frappe.throw(_("Only WIP Transfer of type 'Receive from Supplier' can be used to create Purchase Invoice"))
	
	# 处理选中的项目
	if selected_items and isinstance(selected_items, str):
		import json
		selected_items = json.loads(selected_items)
	
	# 获取已开票数量映射，直接传入文档对象
	invoiced_qty_map = get_invoiced_qty_map(wip_transfer_doc=doc)
	
	# 税率从 Light Mes Settings 获取
	tax_rate = frappe.db.get_single_value("Light Mes Settings", "tax_rate") or 0
	
	# 获取公司的委外加工费科目和委外加工费税科目
	company = doc.company
	account = None
	tax_account = None
	company_doc = frappe.get_doc("Company", company)
	if hasattr(company_doc, "custom_default_subcontract_processing_fee_account"):
		account = company_doc.custom_default_subcontract_processing_fee_account
	if hasattr(company_doc, "custom_default_subcontract_tax_account"):
		tax_account = company_doc.custom_default_subcontract_tax_account
	
	def set_missing_values(source, target):
		# 检查并移除空行
		items_to_remove = []
		for i, item in enumerate(target.get("items")):
			if not item.rate or item.rate <= 0:
				items_to_remove.append(item)
		
		# 从后向前移除空行，避免索引问题
		for item in reversed(items_to_remove):
			target.get("items").remove(item)
		
		if len(target.get("items")) == 0:
			frappe.throw(_("All items have already been invoiced or no valid items selected"))
		
		# 重新设置物料明细行的序号
		for i, item in enumerate(target.get("items"), 1):
			item.idx = i
		
		doc = frappe.get_doc(target)
		
		# 只有当目标文档没有税费明细时才添加
		if tax_account and not doc.taxes:
			doc.append("taxes", {
				"category": "Total",
				"add_deduct_tax": "Add",
				"charge_type": "On Net Total",
				"account_head": tax_account,
				"rate": tax_rate,
				"description": tax_account + "@" + str(tax_rate) + "%",
			})
		
		doc.run_method("onload")
		doc.run_method("set_missing_values")
		doc.run_method("calculate_taxes_and_totals")
		doc.set_payment_schedule()
	
	def get_pending_qty(item_row):
		"""获取待开票数量"""
		# 如果有选中的项目列表，检查当前项目是否在列表中
		if selected_items and item_row.name not in selected_items:
			return 0
			
		# 获取已开票数量
		invoiced_qty = invoiced_qty_map.get(item_row.name, 0)
		# 计算待开票数量 = 良品数量 - 已开票数量
		pending_qty = item_row.qty - invoiced_qty
		return pending_qty
	
	def update_item(source_doc, target_doc, source_parent):
		# 获取待开票数量
		pending_qty = get_pending_qty(source_doc)
			
		target_doc.qty = pending_qty
		target_doc.uom = "个"
		target_doc.conversion_factor = 1
		target_doc.stock_qty = pending_qty
		
		# 设置单价为委外单价
		# 设置物料名称，等于工单成品物料名称 + 工序名
		rate = 0
		if source_doc.job_card:
			job_card = frappe.get_doc("Job Card", source_doc.job_card)
			# 设置物料名称，等于工单成品物料名称 + 工序名
			target_doc.item_name = job_card.item_name + '-' + job_card.operation
			target_doc.custom_fg_item_code = job_card.production_item
			target_doc.custom_operation = job_card.operation
			target_doc.custom_date = source_parent.date
			if job_card.operation_id:
				operation = frappe.get_doc("Work Order Operation", job_card.operation_id)
				rate = operation.custom_service_price or 0
		
		target_doc.rate = rate
		target_doc.amount = flt(pending_qty * rate)
		
		# 设置科目
		target_doc.expense_account = account
		
		# 设置工单和加工单引用
		target_doc.custom_work_order = source_doc.work_order
		target_doc.custom_job_card = source_doc.job_card
		target_doc.custom_wip_transfer = source_name
		target_doc.custom_wip_transfer_item = source_doc.name
		
		# 如果单价为0，则标记为删除
		if rate <= 0:
			target_doc._skip_item = True
	
	# 如果target_doc是字符串，转换为文档对象
	if target_doc and isinstance(target_doc, str):
		import json
		target_doc = frappe.get_doc(json.loads(target_doc))
	
	doclist = get_mapped_doc(
		"Wip Transfer",
		source_name,
		{
			"Wip Transfer": {
				"doctype": "Purchase Invoice",
				"field_map": {
					"from_supplier": "supplier",
					"date": "posting_date",
				},
				"validation": {
					"docstatus": ["=", 1],
				},
			},
			"Wip Transfer Item": {
				"doctype": "Purchase Invoice Item",
				"field_map": {
					"name": "custom_wip_transfer_item",
					"parent": "custom_wip_transfer",
				},
				"postprocess": update_item,
				"condition": lambda d: get_pending_qty(d) != 0,
			},
		},
		target_doc,
		set_missing_values,
	)
	
	return doclist

@frappe.whitelist()
def get_wip_transfer_for_invoice(doctype, txt, searchfield, start, page_len, filters, work_orders=None, as_dict=False):
	"""自定义搜索方法，返回委外入库记录及其相关字段"""
	# 处理filters参数，可能是字符串或字典
	if isinstance(filters, str):
		import json
		filters = json.loads(filters)
	
	# 处理work_orders参数，可能是字符串或列表
	if work_orders and isinstance(work_orders, str):
		import json
		work_orders = json.loads(work_orders)
	
	# 确保start和page_len是整数
	start = int(start)
	page_len = int(page_len)
	
	# 构建查询条件
	conditions = []
	values = {}
	
	if filters.get("docstatus"):
		conditions.append("wt.docstatus = %(docstatus)s")
		values["docstatus"] = filters.get("docstatus")
	
	if filters.get("type"):
		conditions.append("wt.type = %(type)s")
		values["type"] = filters.get("type")
	
	if filters.get("company"):
		conditions.append("wt.company = %(company)s")
		values["company"] = filters.get("company")
	
	if filters.get("from_supplier"):
		conditions.append("wt.from_supplier = %(from_supplier)s")
		values["from_supplier"] = filters.get("from_supplier")
	
	# 处理工单过滤条件
	if work_orders and len(work_orders) > 0:
		conditions.append("wti.work_order IN %(work_orders)s")
		values["work_orders"] = tuple(work_orders)
	
	# 处理日期范围过滤条件
	if filters.get("date_from") and filters.get("date_to"):
		conditions.append("wt.date BETWEEN %(date_from)s AND %(date_to)s")
		values["date_from"] = filters.get("date_from")
		values["date_to"] = filters.get("date_to")
	
	if txt:
		conditions.append("(wt.name LIKE %(txt)s OR wt.from_supplier LIKE %(txt)s OR wo.production_item LIKE %(txt)s OR wo.item_name LIKE %(txt)s)")
		values["txt"] = "%" + txt + "%"
	
	# 添加过滤条件：排除状态为 "Invoiced" 的项目
	conditions.append("(wti.status IS NULL OR wti.status != 'Invoiced')")
	
	# 添加过滤条件：排除已经在草稿状态采购发票中的 WIP Transfer Item
	conditions.append("NOT EXISTS (SELECT 1 FROM `tabPurchase Invoice Item` pii JOIN `tabPurchase Invoice` pi ON pii.parent = pi.name WHERE pii.custom_wip_transfer_item = wti.name AND pi.docstatus = 0)")
	
	# 构建SQL查询，返回所有需要的字段
	sql = """
		SELECT 
			wt.name as wip_transfer_name, 
			wt.date,
			wt.from_supplier,
			wt.company,
			wti.name as wip_transfer_item_name,
			wti.idx as item_idx,
			wti.work_order,
			wti.job_card,
			wti.operation,
			wti.qty,
			wti.scrap_qty,
			wti.status,
			wti.invoiced_qty,
			wo.production_item,
			wo.item_name,
			jc.operation_id,
			woo.custom_service_price as rate
		FROM 
			`tabWip Transfer` wt
		JOIN 
			`tabWip Transfer Item` wti ON wt.name = wti.parent
		LEFT JOIN 
			`tabWork Order` wo ON wti.work_order = wo.name
		LEFT JOIN
			`tabJob Card` jc ON wti.job_card = jc.name
		LEFT JOIN
			`tabWork Order Operation` woo ON jc.operation_id = woo.name
		WHERE 
			{conditions}
		ORDER BY 
			wt.date DESC, wt.name, wti.idx
		LIMIT %(start)s, %(page_len)s
	""".format(conditions=" AND ".join(conditions) if conditions else "1=1")
	
	# 添加分页参数
	values.update({
		"start": start,
		"page_len": page_len
	})
	
	# 执行查询
	result = frappe.db.sql(sql, values, as_dict=as_dict)

	tax_rate = frappe.db.get_single_value("Light Mes Settings", "tax_rate") or 0
	tax_rate = tax_rate / 100  # 转换为小数

	# 获取税率并计算税额和金额
	for item in result:
		# 获取公司的委外加工费科目税率
		# 设置税率
		item["tax_rate"] = tax_rate

		# 计算待开票数量
		item["pending_qty"] = flt(item.get("qty") or 0) - flt(item.get("invoiced_qty") or 0)
		
		# 计算税额和金额
		rate = item.get("rate") or 0
		pending_qty = item.get("pending_qty") or 0
		
		item["tax_amount"] = flt(pending_qty * rate * tax_rate)
		item["amount"] = flt(pending_qty * rate * (1 + tax_rate))
	
	return result

@frappe.whitelist()
def map_selected_items(source_names, selected_items, target_doc=None):
	"""从多个在制品移转的特定项目创建采购发票"""
	# 处理参数
	if isinstance(source_names, str):
		import json
		source_names = json.loads(source_names)
	
	if isinstance(selected_items, str):
		import json
		selected_items = json.loads(selected_items)
	
	if target_doc and isinstance(target_doc, str):
		import json
		target_doc = frappe.get_doc(json.loads(target_doc))
	else:
		target_doc = frappe.new_doc("Purchase Invoice")
	
	# 循环处理每个 WIP Transfer
	for source_name in source_names:
		# 调用 make_purchase_invoice 方法，传递选中的项目
		invoice = make_purchase_invoice(
			source_name=source_name,
			target_doc=target_doc,
			selected_items=selected_items
		)
		target_doc = invoice
	
	# 重新设置物料明细行的序号
	for i, item in enumerate(target_doc.items, 1):
		item.idx = i
	
	return target_doc

@frappe.whitelist()
def get_workshop_receipt_for_sending(doctype, txt, searchfield, filters, work_orders=None, as_dict=True):
	"""查询已车间入库待委外发料的数据"""
	# 处理filters参数，可能是字符串或字典
	if isinstance(filters, str):
		import json
		filters = json.loads(filters)
	
	# 处理work_orders参数，可能是字符串或列表
	if work_orders and isinstance(work_orders, str):
		import json
		work_orders = json.loads(work_orders)
	
	# 构建基本过滤条件
	query_filters = {
		"docstatus": 1,
		"type": "Receive from Workshop",
		"company": filters.get("company")
	}
	
	if filters.get("plant_floor"):
		query_filters["plant_floor"] = filters.get("plant_floor")
	
	# 如果指定了工单过滤条件，直接添加到查询条件中
	if work_orders and len(work_orders) > 0:
		query_filters["work_order"] = ["in", work_orders]
	
	# 构建字段列表，包括主表和子表字段
	fields = [
		"name as wip_transfer_name",
		"date",
		"plant_floor",
		"company",
		"`tabWip Transfer Item`.name as wip_transfer_item_name",
		"`tabWip Transfer Item`.idx as item_idx",
		"`tabWip Transfer Item`.work_order",
		"`tabWip Transfer Item`.job_card as current_job_card",
		"`tabWip Transfer Item`.operation as current_operation",
		"`tabWip Transfer Item`.operation_id as current_operation_id",
		"`tabWip Transfer Item`.qty",
		"`tabWip Transfer Item`.scrap_qty"
	]
	
	# 获取车间入库单及其明细
	workshop_receipts = frappe.get_all(
		"Wip Transfer",
		filters=query_filters,
		fields=fields
	)
	
	# 如果没有车间入库单，直接返回空列表
	if not workshop_receipts:
		return []
	
	# 收集所有工单ID，用于批量查询
	work_order_ids = list(set([item.work_order for item in workshop_receipts if item.work_order]))
	
	# 批量获取工单信息
	work_orders_data = {}
	if work_order_ids:
		for wo in frappe.get_all("Work Order", filters={"name": ["in", work_order_ids]}, 
								fields=["name", "production_item", "item_name"]):
			work_orders_data[wo.name] = wo
	
	# 批量获取所有工单的工序明细，包括车间入库和委外标志以及已发料数量
	all_operations = []
	if work_order_ids:
		all_operations = frappe.get_all(
			"Work Order Operation",
			filters={"parent": ["in", work_order_ids]},
			fields=[
				"name", "idx", "parent", "operation", 
				"custom_supplier", "custom_service_price",
				"custom_enable_wip_receipt", "custom_is_subcontracting",
				"custom_sent_qty"
			],
			order_by="parent, idx"
		)
	
	# 按工单ID组织工序，并预计算下一道工序
	work_order_operations = {}
	next_operations = {}
	
	# 首先按工单ID组织工序
	for op in all_operations:
		wo_id = op.parent
		
		# 初始化工单的工序映射
		if wo_id not in work_order_operations:
			work_order_operations[wo_id] = {}
		
		# 保存工序ID到工序的映射
		work_order_operations[wo_id][op.name] = op
	
	# 为每个工单创建idx到工序的映射，并计算下一道工序
	for wo_id in work_order_operations:
		# 创建idx到工序的映射
		idx_to_op = {}
		for op in work_order_operations[wo_id].values():
			idx_to_op[op.idx] = op
		
		# 计算每个工序的下一道工序，只处理车间入库工序
		for op in work_order_operations[wo_id].values():
			# 只处理车间入库工序
			if op.custom_enable_wip_receipt:
				next_idx = op.idx + 1
				if next_idx in idx_to_op:
					next_op = idx_to_op[next_idx]
					# 只记录下一道工序是委外工序的情况
					if next_op.custom_is_subcontracting:
						next_operations[op.name] = next_op
	
	# 批量获取草稿状态委外发料单中的项目 - 检查是否有相同工单和工序的委外发料单
	in_draft_map = {}
	draft_send_to_supplier_docs = frappe.get_all(
		"Wip Transfer", 
		filters={"type": "Send to Supplier", "docstatus": 0},
		pluck="name"
	)
	
	if draft_send_to_supplier_docs:
		# 获取所有草稿状态委外发料单的明细
		draft_items = frappe.get_all(
			"Wip Transfer Item",
			filters={
				"parent": ["in", draft_send_to_supplier_docs]
			},
			fields=["work_order", "operation_id"]
		)
		
		# 构建草稿状态映射 - 使用工单和工序ID作为键
		for item in draft_items:
			if item.work_order and item.operation_id:
				key = f"{item.work_order}_{item.operation_id}"
				in_draft_map[key] = True
	
	# 批量获取委外工序的加工单
	next_operation_job_cards = {}
	next_operation_ids = []
	
	# 收集所有需要查询的下一道工序ID
	for receipt in workshop_receipts:
		if not receipt.work_order or not receipt.current_operation_id:
			continue
		
		current_op = work_order_operations.get(receipt.work_order, {}).get(receipt.current_operation_id)
		if not current_op:
			continue
		
		next_op = next_operations.get(current_op.name)
		if not next_op:
			continue
		
		next_operation_ids.append(next_op.name)
	
	# 批量查询委外工序的加工单
	if next_operation_ids:
		job_cards = frappe.get_all(
			"Job Card",
			filters={
				"operation_id": ["in", next_operation_ids],
				"docstatus": 0
			},
			fields=["name", "operation_id", "work_order"]
		)
		
		# 构建工序ID到加工单的映射
		for jc in job_cards:
			key = f"{jc.work_order}_{jc.operation_id}"
			next_operation_job_cards[key] = jc.name
	
	# 处理结果
	result = []
	for receipt in workshop_receipts:
		# 跳过没有工单的项目
		if not receipt.work_order or not receipt.current_operation_id:
			continue
		
		# 获取工单信息
		work_order = work_orders_data.get(receipt.work_order)
		if not work_order:
			continue
		
		# 获取当前工序
		current_op = work_order_operations.get(receipt.work_order, {}).get(receipt.current_operation_id)
		if not current_op:
			continue
		
		# 检查当前工序是否是车间入库工序
		if not current_op.custom_enable_wip_receipt:
			continue
		
		# 获取下一道工序 - 使用预先计算的映射
		next_op = next_operations.get(current_op.name)
		
		# 如果没有下一道工序，或下一道工序不是委外工序，跳过
		if not next_op:
			continue
		
		# 如果指定了供应商过滤条件，检查是否匹配
		if filters.get("from_supplier") and next_op.custom_supplier != filters.get("from_supplier"):
			continue
		
		# 如果指定了搜索文本，检查是否匹配
		if txt:
			txt_lower = txt.lower()
			if (txt_lower not in receipt.wip_transfer_name.lower() and 
				txt_lower not in (work_order.production_item or "").lower() and 
				txt_lower not in (work_order.item_name or "").lower() and 
				txt_lower not in (receipt.current_operation or "").lower() and 
				txt_lower not in (next_op.operation or "").lower()):
				continue
		
		# 获取已发料数量 - 直接使用工序明细中的已发料数量字段
		sent_qty = next_op.custom_sent_qty or 0
		
		# 计算可发料数量
		available_qty = receipt.qty - sent_qty
		
		# 如果可发料数量小于等于0，跳过
		if available_qty <= 0:
			continue
		
		# 检查是否已经在草稿状态委外发料单中 - 使用工单和下一道工序ID作为键
		key = f"{receipt.work_order}_{next_op.name}"
		if in_draft_map.get(key):
			continue
		
		# 获取委外工序的加工单
		next_job_card = next_operation_job_cards.get(f"{receipt.work_order}_{next_op.name}")
		
		# 添加到结果列表
		result.append({
			"wip_transfer_name": receipt.wip_transfer_name,
			"date": receipt.date,
			"plant_floor": receipt.plant_floor,
			"company": receipt.company,
			"wip_transfer_item_name": receipt.wip_transfer_item_name,
			"item_idx": receipt.item_idx,
			"work_order": receipt.work_order,
			"current_job_card": receipt.current_job_card,
			"current_operation": receipt.current_operation,
			"current_operation_id": receipt.current_operation_id,
			"qty": receipt.qty,
			"scrap_qty": receipt.scrap_qty,
			"next_operation_id": next_op.name,
			"next_operation": next_op.operation,
			"next_job_card": next_job_card,  # 添加委外工序的加工单
			"from_supplier": next_op.custom_supplier,
			"production_item": work_order.production_item,
			"item_name": work_order.item_name,
			"sent_qty": sent_qty,
			"available_qty": available_qty
		})
	
	return result

@frappe.whitelist()
def get_subcontracting_operations_for_sending(doctype, txt, searchfield, filters, work_orders=None, as_dict=True):
	"""查询待委外发料的工序数据"""
	# 处理filters参数，可能是字符串或字典
	if isinstance(filters, str):
		import json
		filters = json.loads(filters)
	
	# 处理work_orders参数，可能是字符串或列表
	if work_orders and isinstance(work_orders, str):
		import json
		work_orders = json.loads(work_orders)
	
	# 构建基本过滤条件
	work_order_filters = {
		"docstatus": 1,
		"company": filters.get("company")
	}
	
	# 如果指定了工单过滤条件，直接添加到查询条件中
	if work_orders and len(work_orders) > 0:
		work_order_filters["name"] = ["in", work_orders]
	
	# 获取所有符合条件的工单
	work_orders_data = {}
	for wo in frappe.get_all("Work Order", 
							filters=work_order_filters, 
							fields=["name", "production_item", "item_name"]):
		work_orders_data[wo.name] = wo
	
	# 如果没有工单，直接返回空列表
	if not work_orders_data:
		return []
	
	# 获取所有工单的工序明细
	all_operations = frappe.get_all(
		"Work Order Operation",
		filters={"parent": ["in", list(work_orders_data.keys())]},
		fields=[
			"name", "idx", "parent", "operation", 
			"custom_supplier", "custom_service_price",
			"custom_enable_wip_receipt", "custom_is_subcontracting",
			"custom_sent_qty"
		],
		order_by="parent, idx"
	)
	
	# 按工单ID组织工序
	work_order_operations = {}
	for op in all_operations:
		wo_id = op.parent
		
		# 初始化工单的工序映射
		if wo_id not in work_order_operations:
			work_order_operations[wo_id] = {}
		
		# 保存工序ID到工序的映射
		work_order_operations[wo_id][op.name] = op
	
	# 为每个工单创建idx到工序的映射，并找出委外工序及其前一道工序
	subcontracting_operations = []
	previous_operations = {}
	
	for wo_id, operations in work_order_operations.items():
		# 创建idx到工序的映射
		idx_to_op = {}
		for op in operations.values():
			idx_to_op[op.idx] = op
		
		# 找出所有委外工序及其前一道工序
		for op in operations.values():
			# 如果指定了供应商过滤条件，检查是否匹配
			if filters.get("from_supplier") and op.custom_supplier != filters.get("from_supplier"):
				continue
				
			if op.custom_is_subcontracting:
				# 找前一道工序
				prev_idx = op.idx - 1
				if prev_idx in idx_to_op and idx_to_op[prev_idx].custom_enable_wip_receipt:
					previous_operations[op.name] = idx_to_op[prev_idx]
					subcontracting_operations.append(op)
	
	# 如果没有委外工序，直接返回空列表
	if not subcontracting_operations:
		return []
	
	# 批量获取草稿状态委外发料单中的项目
	in_draft_map = {}
	draft_send_to_supplier_docs = frappe.get_all(
		"Wip Transfer", 
		filters={"type": "Send to Supplier", "docstatus": 0},
		pluck="name"
	)
	
	if draft_send_to_supplier_docs:
		# 获取所有草稿状态委外发料单的明细
		draft_items = frappe.get_all(
			"Wip Transfer Item",
			filters={
				"parent": ["in", draft_send_to_supplier_docs]
			},
			fields=["work_order", "operation_id"]
		)
		
		# 构建草稿状态映射 - 使用工单和工序ID作为键
		for item in draft_items:
			if item.work_order and item.operation_id:
				key = f"{item.work_order}_{item.operation_id}"
				in_draft_map[key] = True
	
	# 批量获取委外工序的加工单
	operation_job_cards = {}
	operation_ids = [op.name for op in subcontracting_operations]
	
	if operation_ids:
		job_cards = frappe.get_all(
			"Job Card",
			filters={
				"operation_id": ["in", operation_ids],
				"docstatus": 0
			},
			fields=["name", "operation_id", "work_order"]
		)
		
		# 构建工序ID到加工单的映射
		for jc in job_cards:
			key = f"{jc.work_order}_{jc.operation_id}"
			operation_job_cards[key] = jc.name
	
	# 批量获取车间入库单数据
	workshop_receipts = {}
	
	# 构建车间入库单过滤条件
	receipt_filters = {
		"docstatus": 1,
		"type": "Receive from Workshop",
		"company": filters.get("company")
	}
	
	# 添加车间过滤条件
	if filters.get("plant_floor"):
		receipt_filters["plant_floor"] = filters.get("plant_floor")
	
	# 添加日期范围过滤条件
	if filters.get("date_from") and filters.get("date_to"):
		receipt_filters["date"] = ["between", [filters.get("date_from"), filters.get("date_to")]]
	
	# 获取所有车间入库单
	all_receipts = frappe.get_all(
		"Wip Transfer",
		filters=receipt_filters,
		fields=[
			"name",
			"date",
			"plant_floor",
			"`tabWip Transfer Item`.name as item_name",
			"`tabWip Transfer Item`.work_order",
			"`tabWip Transfer Item`.operation_id",
			"`tabWip Transfer Item`.qty"
		]
	)
	
	# 按工单和工序ID组织车间入库数据
	for receipt in all_receipts:
		if not receipt.work_order or not receipt.operation_id:
			continue
		
		key = f"{receipt.work_order}_{receipt.operation_id}"
		if key not in workshop_receipts:
			workshop_receipts[key] = {
				"total_qty": 0,
				"receipts": []
			}
		
		workshop_receipts[key]["total_qty"] += receipt.qty
		workshop_receipts[key]["receipts"].append(receipt)
	
	# 处理结果
	result = []
	for op in subcontracting_operations:
		# 获取工单信息
		work_order = work_orders_data.get(op.parent)
		if not work_order:
			continue
		
		# 获取前一道工序
		prev_op = previous_operations.get(op.name)
		if not prev_op:
			continue
		
		# 获取前一道工序的车间入库数量
		prev_key = f"{op.parent}_{prev_op.name}"
		receipt_data = workshop_receipts.get(prev_key, {"total_qty": 0, "receipts": []})
		received_qty = receipt_data["total_qty"]
		
		# 获取已发料数量
		sent_qty = op.custom_sent_qty or 0
		
		# 计算可发料数量
		available_qty = received_qty - sent_qty
		
		# 如果可发料数量小于等于0，跳过
		if available_qty <= 0:
			continue
		
		# 如果指定了搜索文本，检查是否匹配
		if txt:
			txt_lower = txt.lower()
			if (txt_lower not in (work_order.production_item or "").lower() and 
				txt_lower not in (work_order.item_name or "").lower() and 
				txt_lower not in (op.operation or "").lower() and 
				txt_lower not in (prev_op.operation or "").lower()):
				continue
		
		# 检查是否已经在草稿状态委外发料单中
		key = f"{op.parent}_{op.name}"
		if in_draft_map.get(key):
			continue
		
		# 获取委外工序的加工单
		job_card = operation_job_cards.get(key)
		
		# 获取车间信息
		plant_floor = ""
		date = None
		if receipt_data["receipts"]:
			plant_floor = receipt_data["receipts"][0].plant_floor
			date = receipt_data["receipts"][0].date
		
		# 添加到结果列表
		result.append({
			"work_order": op.parent,
			"operation_id": op.name,
			"operation": op.operation,
			"previous_operation_id": prev_op.name,
			"previous_operation": prev_op.operation,
			"job_card": job_card,
			"supplier": op.custom_supplier,
			"production_item": work_order.production_item,
			"item_name": work_order.item_name,
			"received_qty": received_qty,
			"sent_qty": sent_qty,
			"available_qty": available_qty,
			"plant_floor": plant_floor,
			"date": date
		})
	
	return result

@frappe.whitelist()
def make_wip_transfer_return(source_name, target_doc=None):
	"""创建在制品移转退货单"""
	from frappe.model.mapper import get_mapped_doc
	
	def set_missing_values(source, target):
		target.is_return = 1
		target.return_against = source.name
		target.ignore_pricing_rule = 1
		
		# 复制表头字段
		if source.type == "Send to Supplier":
			target.type = "Send to Supplier"  # 发料退货仍然是发料类型
		elif source.type == "Receive from Supplier":
			target.type = "Receive from Supplier"  # 入库退货仍然是入库类型
	
	def update_item(source_doc, target_doc, source_parent):
		# 获取已退货数量
		returned_qty = frappe.db.get_value("Wip Transfer Item", 
										  {"wip_transfer_item": source_doc.name, 
										   "docstatus": 1}, 
										  "sum(qty)") or 0
		
		# 计算可退货数量
		returnable_qty = source_doc.qty - returned_qty
		
		# 设置退货数量为负数
		target_doc.qty = -1 * returnable_qty

		# 设置废品数量为0
		target_doc.scrap_qty = 0
		
		# 设置总数量
		if hasattr(target_doc, 'total_qty'):
			target_doc.total_qty = target_doc.qty + (target_doc.scrap_qty or 0)
		
		# 记录原始移转单明细ID
		target_doc.wip_transfer_item = source_doc.name
	
	doclist = get_mapped_doc("Wip Transfer", source_name, {
		"Wip Transfer": {
			"doctype": "Wip Transfer",
			"validation": {
				"docstatus": ["=", 1]
			}
		},
		"Wip Transfer Item": {
			"doctype": "Wip Transfer Item",
			"field_map": {
				"name": "wip_transfer_item"
			},
			"postprocess": update_item
		}
	}, target_doc, set_missing_values)
	
	return doclist



